/*
 * Created by Jaren at 2021/8/17 19:46
 */
import 'dart:typed_data';
import 'package:inmotion_protocol/base/codec.dart';
import 'package:inmotion_protocol/lorin/constant.dart';
import 'package:inmotion_protocol/lorin/lorin_message.dart';
import 'package:inmotion_protocol/lorin/factory/lorin_message_factory.dart';
import 'package:inmotion_protocol/lorin/util.dart';
import 'package:inmotion_protocol/util/byte_util.dart';
import 'package:inmotion_protocol/util/int8_list_buffer.dart';
import 'package:inmotion_protocol/util/log.dart';

import 'factory/general/general_message.factory.dart';

class LorinDecoder extends Decoder<LorinMessage> {
  @override
  bool isStartWithPacketHead(Uint8ClampedList bytes) {
    return bytes.length >= LorinMessage.HEAD_BYTE_LENGTH && ByteUtil.byteEquals(bytes[0], LorinMessage.HEAD_BYTE1) && ByteUtil.byteEquals(bytes[1], LorinMessage.HEAD_BYTE2);
  }

  @override
  int findValidPacketEndIndex(Int8ListBuffer bytes) {
    // 标记原来的readerIndex
    int oldReaderIndex = bytes.readerIndex;

    buffer.readerIndex = 0;
    int totalLength = buffer.readableBytes();
    if (totalLength < LorinMessage.MIN_LENGTH) {
      LogUtils.e("validatePacket", Exception("Total length in buffer is less than min length[" + LorinMessage.MIN_LENGTH.toString() + "]."));
      // 恢复readerIndex
      buffer.readerIndex = oldReaderIndex;
      return -1;
    }

    buffer.readerIndex = LorinMessage.POSITION_FORMAT;
    int format = buffer.readByte();
    int keyLength = LorinMessage.hasKey(format) ? 1 : 0;
    int tabLength = LorinMessage.hasTab(format) ? Util.calTabLength(buffer, LorinMessage.HEAD_BYTE_LENGTH + 1 + keyLength /* 2 head bytes + 1 format byte + 0/1 key length */) : 0;
    int lengthPosition = LorinMessage.HEAD_BYTE_LENGTH + 1 + keyLength + tabLength /* 2 head bytes + 1 format byte + 0~1 key length + 0~4 tab length */;

    buffer.readerIndex = lengthPosition;
    int msgDataLength = buffer.readByte().toUnsigned(8);

    int checkLength = Util.calCheckLength(format);

    /* 2 head + 1 format + 0~1 key length + 0~4 tab length + 1 length + length (of data) + 0/1/2/4 check */
    int expectLength = LorinMessage.HEAD_BYTE_LENGTH + 1 + keyLength + tabLength + 1 + msgDataLength + checkLength;

    // 恢复readerIndex
    buffer.readerIndex = oldReaderIndex;
    if (expectLength == totalLength) {
      return 0;
    }
    if (totalLength < expectLength) {
      return -1;
    }
    return expectLength;
  }

  @override
  int minMsgLength() {
    return LorinMessage.MIN_LENGTH;
  }

  bool lastEscapeChar = false;

  @override
  Uint8ClampedList unescape(Uint8ClampedList bytes) {
    // 是否存在转义字符标志位
    bool hasEscapeChar = false;

    Int8ListBuffer buffer = Int8ListBuffer();
    // 上个转义字符的index
    int lastEscapeIndex = -99999;
    for (int i = 0, length = bytes.length; i < length; i++) {
      if (ByteUtil.byteEquals(bytes[i], Constant.escapeChar)) {
        LogUtils.d("lastEscapeIndex->" + lastEscapeIndex.toString() + ", i->" + i.toString());
        if (lastEscapeIndex + 1 != i) {
          if (i == 0 && lastEscapeChar) {
            // 第一个字符为转义字符a5，且上次转义的最后一个字符也为转义字符a5
            // 保留这个a5
            LogUtils.d("上次转义的最后一个字符为转义字符，本包首字节转义字符保留");
            lastEscapeChar = false;
          } else {
            hasEscapeChar = true;
            lastEscapeIndex = i;
            lastEscapeChar = false;
            LogUtils.d("转义字符，跳过");
            if (i == length - 1) {
              // 最后一个字符标记
              lastEscapeChar = true;
              LogUtils.d("转义字符，最后一个字符");
            }
            continue;
          }
        }
        LogUtils.d("连着2个转义字符，保留");
      }
      lastEscapeChar = false;
      buffer.writeByte(bytes[i]);
    }

    // 若原bytes中没有转义字符，直接返回
    if (!hasEscapeChar) {
      return bytes;
    }
    return buffer.buffer.sublist(0, buffer.readableBytes());
  }

  @override
  LorinMessage parse(Uint8ClampedList buf) {
    int index = 0;
    index += 2; // 2byte head
    int format = buf[index];
    index++;

    int key = 0;
    if (LorinMessage.hasKey(format)) {
      key = buf[index];
      index++;
    }
    Uint8ClampedList? tab;
    if (LorinMessage.hasTab(format)) {
      int tabLength = Util.calTabLength(buf, LorinMessage.HEAD_BYTE_LENGTH + 1 + (LorinMessage.hasKey(format) ? 1 : 0));
      tab = buf.sublist(index, index + tabLength);
      index += tabLength;
    }
    int dataLength = ByteUtil.toUint8(buf[index]).value;
    index++;

    Uint8ClampedList data = buf.sublist(index, index + dataLength);
    index += dataLength;

    Uint8ClampedList? check;
    if (LorinMessage.hasCheck(format)) {
      int checkLength = Util.calCheckLength(format);
      check = buf.sublist(index, index + checkLength);
      index += checkLength;
    }

    return LorinGeneralMessageFactory.genMsg(format, key, tab, dataLength, data, check);
  }

  @override
  void decrypt(Int8ListBuffer bytes) {}

  @override
  bool check(Uint8ClampedList bytes) {
    if (bytes.length < LorinMessage.MIN_LENGTH) {
      return false;
    }
    // 解析出的校验值的长度
    Uint8ClampedList checkBytes;
    // 计算出的期望校验值
    Uint8ClampedList expectedCheckBytes;

    // 需要校验的数据, 从format位（包括format）以后的数据
    int lengthNeedCheck = LorinMessage.getBytesNeedCheckLength(bytes, 0);

    // format
    int format = bytes[LorinMessage.POSITION_FORMAT];
    // 根据format确认校验方式
    if (Util.isEvenParityCheck(format)) {
      checkBytes = Uint8ClampedList(1);
      expectedCheckBytes = Util.calculateEvenParity(bytes, LorinMessage.HEAD_BYTE_LENGTH, lengthNeedCheck);
    } else if (Util.isCRC16(format)) {
      checkBytes = Uint8ClampedList(2);
      expectedCheckBytes = Util.crc16(bytes, LorinMessage.HEAD_BYTE_LENGTH, lengthNeedCheck);
    } else if (Util.isCRC32(format)) {
      checkBytes = Uint8ClampedList(4);
      expectedCheckBytes = Util.crc32(bytes, LorinMessage.HEAD_BYTE_LENGTH, lengthNeedCheck);
    } else {
      // 无校验
      return true;
    }

    // 拷贝赋值checkBytes
    ByteUtil.copy(bytes, bytes.length - checkBytes.length, checkBytes, 0, checkBytes.length);
    ByteUtil.reverse(bytes);

    bool result = ByteUtil.arrayEquals(checkBytes, expectedCheckBytes);
    if (!result) {
      LogUtils.w("Check failed! Expected [" + ByteUtil.bytes2HexString(expectedCheckBytes) + "] but found [" + ByteUtil.bytes2HexString(checkBytes) + "]");
    }
    return result;
  }

  @override
  int findPacketHead(Uint8ClampedList bytes) {
    int lastHead1Index = -1;
    for (int i = 0; i < bytes.length; i++) {
      if (ByteUtil.byteEquals(bytes[i], LorinMessage.HEAD_BYTE1)) {
        if (lastHead1Index >= 0) {
          return lastHead1Index;
        } else {
          lastHead1Index = i;
        }
      } else {
        lastHead1Index = -1;
      }
    }
    return -1;
  }
}

class LorinEncoder extends Encoder<LorinMessage> {
  @override
  Uint8ClampedList encrypt(Uint8ClampedList bytes) {
    return Cryptor.getInstance().encrypt(bytes);
  }

  @override
  Uint8ClampedList msgToBytes(LorinMessage msg) {
    buffer.clear();
    // 消息总长度
    int totalLength = msg.getTotalLength();

    buffer.clear();

    buffer.writeBytes(msg.head);

    buffer.writeByte(msg.format.value);

    if (LorinMessage.hasKey(msg.format.value)) {
      buffer.writeByte(msg.key!.value);
    }

    if (LorinMessage.hasTab(msg.format.value)) {
      buffer.writeBytes(msg.tab!);
    }

    buffer.writeByte(msg.length.value);
    buffer.writeBytes(msg.data.toBytes());
    if (LorinMessage.hasCheck(msg.format.value)) {
      buffer.writeBytes(msg.check!);
    }

    Uint8ClampedList msgBytes = Uint8ClampedList(totalLength);
    buffer.readBytes(msgBytes);
    // 清空buffer
    buffer.clear();

    if (LorinMessage.hasKey(msg.format.value)) {
      // 加密
      Cryptor.getInstance().encrypt(msgBytes);
    }
    return escape(msgBytes);
  }

  Int8ListBuffer sBufForEscape = Int8ListBuffer();

  Uint8ClampedList escape(Uint8ClampedList bytes) {
    sBufForEscape.clear();

    // 包头不参与转义
    sBufForEscape.writeByte(bytes[0]);
    sBufForEscape.writeByte(bytes[1]);

    // 参与转义的长度length-2（从包头后算起）
    for (int i = 2, escapeLength = bytes.length; i < escapeLength; i++) {
      if (ByteUtil.byteEquals(bytes[i], Constant.char1NeedEscape) ||
          ByteUtil.byteEquals(bytes[i], Constant.char2NeedEscape) ||
          ByteUtil.byteEquals(bytes[i], Constant.char3NeedEscape)) {
        sBufForEscape.writeByte(Constant.escapeChar);
      }
      sBufForEscape.writeByte(bytes[i]);
    }

    Uint8ClampedList bytesAfterEscape = Uint8ClampedList(sBufForEscape.readableBytes());
    sBufForEscape.readBytes(bytesAfterEscape);
    return bytesAfterEscape;
  }

  @override
  bool needEncrypt(LorinMessage msg) {
    return LorinMessage.hasKey(msg.format.value);
  }
}

class Cryptor {
  static final Uint8ClampedList sCryptTable = Uint8ClampedList.fromList([
    0xED,
    0xB9,
    0xF2,
    0x58,
    0x8B,
    0xE3,
    0xC5,
    0xEC,
    0x14,
    0xC0,
    0x9D,
    0x42,
    0x60,
    0x17,
    0x0F,
    0x9E,
    0x0B,
    0x98,
    0xDC,
    0x16,
    0x9D,
    0xDF,
    0x66,
    0xD1,
    0xC3,
    0x9C,
    0xC7,
    0xBF,
    0x4D,
    0xFD,
    0xBD,
    0xCE,
    0xEB,
    0x95,
    0xE6,
    0x96,
    0x25,
    0x4F,
    0xA8,
    0x8D,
    0x52,
    0x49,
    0x96,
    0xD0,
    0xC2,
    0x80,
    0xBB,
    0xCC,
    0x90,
    0x02,
    0x09,
    0x3E,
    0x1E,
    0x7B,
    0x39,
    0xF3,
    0x47,
    0x4A,
    0x20,
    0x00,
    0x6F,
    0x9B,
    0x2F,
    0xA9,
    0x1A,
    0x50,
    0xA2,
    0x0E,
    0x06,
    0x21,
    0x9C,
    0xFB,
    0x1E,
    0x59,
    0x76,
    0x4A,
    0x83,
    0xD3,
    0xCA,
    0x4D,
    0xDC,
    0xFB,
    0x31,
    0xC2,
    0xA6,
    0xDE,
    0x14,
    0x11,
    0x60,
    0x99,
    0x3E,
    0xC6,
    0x7A,
    0x41,
    0xC5,
    0x5B,
    0xF6,
    0xCB,
    0xD9,
    0x4D,
    0x4F,
    0x62,
    0x2B,
    0x15,
    0x3D,
    0xAF,
    0x4D,
    0xCB,
    0x7A,
    0x6D,
    0x32,
    0x40,
    0xAD,
    0x38,
    0xE7,
    0x7A,
    0x3B,
    0x1D,
    0x25,
    0x19,
    0x3D,
    0x3E,
    0xF4,
    0xE1,
    0xD5,
    0x32,
    0x8E,
    0xF2,
    0xF1,
    0x40,
    0xA4,
    0x4E,
    0xD9,
    0x83,
    0xDC,
    0x90,
    0x44,
    0x64,
    0x6C,
    0xEB,
    0x02,
    0xCC,
    0xA6,
    0x6E,
    0x6E,
    0x2B,
    0x6F,
    0x8B,
    0x67,
    0x09,
    0x3B,
    0xD2,
    0x4E,
    0x8C,
    0x3F,
    0x95,
    0x99,
    0x9C,
    0xC5,
    0x66,
    0x67,
    0x7B,
    0xA5,
    0xD7,
    0xBA,
    0x89,
    0xE9,
    0x72,
    0x4D,
    0xA1,
    0xD6,
    0x57,
    0xF6,
    0x35,
    0x54,
    0x9D,
    0x2D,
    0xE0,
    0x5B,
    0x6A,
    0x68,
    0x66,
    0x05,
    0xA8,
    0x0F,
    0x07,
    0xEB,
    0xEE,
    0x56,
    0x31,
    0x77,
    0x94,
    0x05,
    0xF8,
    0x07,
    0xED,
    0xE7,
    0x87,
    0x98,
    0x3F,
    0x3E,
    0xB2,
    0xB1,
    0x31,
    0xFA,
    0x2D,
    0x94,
    0x7D,
    0x99,
    0xC6,
    0x1F,
    0x83,
    0x66,
    0xD9,
    0x11,
    0x32,
    0x15,
    0x8B,
    0xF8,
    0x04,
    0x61,
    0x8E,
    0x04,
    0x81,
    0xF0,
    0x92,
    0x80,
    0xD9,
    0x7E,
    0xB7,
    0xD4,
    0x24,
    0xD1,
    0xA4,
    0xAD,
    0x21,
    0x89,
    0x81,
    0x55,
    0x8D,
    0x50,
    0x56,
    0x6A,
    0x28,
    0xD8,
    0xD5,
    0x82,
    0x89,
    0x5A,
    0x1C,
    0xEA,
    0x42,
    0x2A,
    0x08,
    0x6A,
    0xDF
  ]);

  static final Uint8ClampedList sAesKey = Uint8ClampedList.fromList([0xA2, 0x3C, 0xBC, 0x5F, 0x11, 0x4B, 0xA1, 0xD5, 0x42, 0x26, 0xE6, 0x39, 0x9E, 0xB3, 0x2F, 0xD1]);

  static const int CRYPT_TABLE_LENGTH = 256;

  static CryptorH sHolder = CryptorH();

  static Cryptor getInstance() {
    return sHolder.cryptor!;
  }

  Uint8ClampedList? cryptTable;

  Cryptor() {
    // 默认使用静态密码表
    genDynamicCryptTableByOriginKey(Uint8ClampedList(0));
  }

  void genDynamicCryptTableByOriginKey(Uint8ClampedList originKey) {
    cryptTable = genDynamicCryptTable(sCryptTable, genCryptTableByByteArray(originKey));
  }

  bool needEncryptOrDecrypt() {
    return cryptTable != null && cryptTable!.length == CRYPT_TABLE_LENGTH;
  }

  /*
     * 加密
     *
     * @param bytes
     * @param index 随机KEY index
     */
  Uint8ClampedList encrypt(Uint8ClampedList bytes, [int index = -1]) {
    if (!needEncryptOrDecrypt()) return bytes;
    if (index == -1) index = ByteUtil.byte2UInt(bytes[LorinMessage.POSITION_CRYPT_KEY]);
    // 从key位后开始加密
    for (int i = LorinMessage.POSITION_CRYPT_KEY + 1, length = bytes.length; i < length; i++) {
      index %= Cryptor.CRYPT_TABLE_LENGTH;
      bytes[i] = (bytes[i] ^ cryptTable![index++]);
    }
    return bytes;
  }

  /*
     * 加密
     *
     * @param buffer
     * @param start
     * @param length
     * @param index  随机KEY index
     */
  void encrypt2(Int8ListBuffer buffer, int start, int length, int index) {
    if (!needEncryptOrDecrypt()) return;
    int oldReaderIndex = buffer.readerIndex;
    int oldWriterIndex = buffer.writerIndex;
    // 从start后开始加密
    for (int i = start; i < start + length; i++) {
      index %= Cryptor.CRYPT_TABLE_LENGTH;
      buffer.readerIndex = i;
      int b = buffer.readByte();
      buffer.readerIndex = 0;
      buffer.writerIndex = i;
      int crypt = cryptTable![index++];
      buffer.writeByte((b ^ crypt));
      buffer.writerIndex = oldWriterIndex;
    }
    buffer.readerIndex = oldReaderIndex;
  }

  void decrypt2(Int8ListBuffer buffer, int start, int length, int index) {
    encrypt2(buffer, start, length, index);
  }

  /*
     * 解密
     *
     * @param bytes
     * @param index 随机KEY index
     */
  void decrypt(Uint8ClampedList bytes, [int index = -1]) {
    if (!needEncryptOrDecrypt()) return;
    if (index == -1) index = ByteUtil.byte2UInt(bytes[LorinMessage.POSITION_CRYPT_KEY]);
    // 加解密过程相同
    encrypt(bytes, index);
  }

  static Uint8ClampedList genCryptTableByByteArray(Uint8ClampedList bytes) {
    if (bytes.isEmpty) {
      return Uint8ClampedList(CRYPT_TABLE_LENGTH);
    }
    int originLength = bytes.length;
    if (originLength == CRYPT_TABLE_LENGTH) {
      return bytes;
    }
    Uint8ClampedList rst = Uint8ClampedList(CRYPT_TABLE_LENGTH);
    if (originLength > CRYPT_TABLE_LENGTH) {
      ByteUtil.copy(bytes, 0, rst, 0, CRYPT_TABLE_LENGTH);
      return rst;
    }
    for (int i = 0, max = CRYPT_TABLE_LENGTH ~/ originLength; i < max; i++) {
      ByteUtil.copy(bytes, 0, rst, i * originLength, originLength);
    }

    int copiedLength = originLength * (CRYPT_TABLE_LENGTH ~/ originLength);

    ByteUtil.copy(bytes, 0, rst, copiedLength, CRYPT_TABLE_LENGTH - copiedLength);

    return rst;
  }

  static Uint8ClampedList genDynamicCryptTable(Uint8ClampedList table1, Uint8ClampedList table2) {
    Uint8ClampedList rst = Uint8ClampedList(CRYPT_TABLE_LENGTH);
    if (table1.length != table2.length || table1.length != CRYPT_TABLE_LENGTH) {
      return rst;
    }

    for (int i = 0; i < CRYPT_TABLE_LENGTH; i++) {
      rst[i] = (table1[i] ^ table2[i]);
    }

    return rst;
  }
}

class CryptorH {
  Cryptor? cryptor;

  CryptorH() {
    cryptor = Cryptor();
  }
}
